/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package main.NER.Trie;

import java.io.Serializable;
import main.Trie.AbstractTrie;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import main.Concurrent.Iteration.IterationHelper;
import main.Concurrent.Iteration.IterationOperation;
import main.KBProcess.Element;
import main.KBProcess.Fragment;
import main.KBProcess.KnowledgeBase;
import main.NER.Branch;
import main.NER.ContextCandidate;
import main.NER.DefaultStem;
import main.NER.Stem;
import main.NER.Trie.CandidateExtractor.BOOTSTRAP;
import main.Ontology.ElementOntologyFactory;
import main.Ontology.ElementOntologySet;
import main.initiate.UTEES_Index;

/**
 * 
 * @author Isaac Osesina
 */
public class ContextTrie extends AbstractTrie<ContextTrieNode, Stem> implements Serializable, Cloneable {

    private ElementOntologyFactory elementOntologyFactory;
    private ORIENTATION orientation;

    public static enum ORIENTATION {
        LEFT, RIGHT
    };

    public ContextTrie(ElementOntologyFactory elementOntologyFactory,
            ORIENTATION orientation) {
        super();
        this.elementOntologyFactory = elementOntologyFactory;
        this.orientation = orientation;
    }

    @Override
    public ContextTrieNode createNode() {
        return new ContextTrieNode();
    }

    public String getContext(Fragment f) {
        String context = null;
        if (f != null) {
            if (orientation == ORIENTATION.LEFT) {
                context = reverseStringIfLeft(f.getLeftContext());
            } else {
                context = f.getRightContext();
            }
        }
        return context;
    }

    public String reverseStringIfLeft(String context) {
        if (context != null) {
            if (orientation == ORIENTATION.LEFT) {
                context = new StringBuffer(context).reverse().toString();
            }
        }
        return context;
    }

    public Stem put(Fragment fragment) {
        if (fragment != null) {
            String context = getContext(fragment);
            if (context != null) {
                return put(context, null, fragment);
            }
        }
        return null;
    }

    public Stem put(String context, Fragment fragment) {
        if (fragment != null) {
            return put(reverseStringIfLeft(context), null, fragment);
        }
        return null;
    }
    
    
    public Stem put(String context, Integer totalFreq)  {
        return put(reverseStringIfLeft(context), totalFreq, null);
    }


    public boolean isAppend(String context) {
        return UTEES_Index.isTaggedText(context);
    }
    

    private Stem put(String keyParameter, Integer totalFreq, Fragment fragment) {

        String key = keyParameter;
        String usedKey = "";
        ElementOntologySet branchID;
        if (fragment != null)
            branchID = fragment.getOntologies();
        else
            branchID = elementOntologyFactory.createEmptyOntologySet();
        /* Ignore ineligible keys */
        if (key == null || key.length() == 0) {
            return null;
        }
        ContextTrieNode currNode = root;
        ContextTrieNode nextNode = null;

        /* Walk down the Trie, inserting nodes where necessary. */
        boolean isAppend = isAppend(keyParameter);
        while (key.length() > 0) {
            usedKey = usedKey + key.charAt(0);
            nextNode = currNode.findChild(key.charAt(0));
            if (nextNode == null) {
                nextNode = currNode.addChild(key.charAt(0), new DefaultStem());
                nextNode.getValue().setStemID(nextNode);
            }
            Stem stem = nextNode.getValue();
            Integer freq = totalFreq != null ? totalFreq : isAppend ? null : 1;
            stem.addBranch(branchID, isAppend, freq, fragment);
            currNode = nextNode;
            key = key.substring(1);
        }

        Stem returnThisObject = nextNode.getValue();
        if (!isAppend) {
            returnThisObject.updateFruit_Terminator(branchID, fragment);
        }

        return returnThisObject;
    }

    /**
     * Returns a Set view of all Leaf nodes values in this Trie.
     * 
     * @return a Set containing all of the leaf values in this Trie. This is the
     *         same as terminating context Stems
     */
    public Set<Stem> leafStemSet() {
        return root.leafStemSet("");
    }

    @Override
    public Set<String> leafKeySet() {
        return root.leafKeySet("");
    }

    /**
     * Returns a Set view of all Leaf nodes values that are ambiguous in this
     * Trie.
     * 
     * @return a Set containing all of the leaf values that are ambiguous in
     *         this Trie. This is the same as terminating context Stems It does
     *         not include contexts that have been appended
     */
    public Set<Stem> ambiguousLeafStemSet() {
        return root.ambiguousStemSet("");
    }

    public Set<String> ambiguousKeySet() {
        return root.ambiguousKeySet("");
    }

    public Set<Map.Entry<String, Stem>> nonAmbiguousEntrySet() {
        return root.nonAmbiguousKeySet("");
    }

    private void appendAmbiguousLeafs(List<KnowledgeBase> kbList) {
        Set<Stem> ambiguousLeafStemSet = ambiguousLeafStemSet();
        if (ambiguousLeafStemSet != null) {
            for (Stem ambiguousLeafStem : ambiguousLeafStemSet) {
                if (!ambiguousLeafStem.isAppend()) {
                    Set<Branch> ambiguousLeafBranches = ambiguousLeafStem
                            .getAmbiguousLeafBranches();
                    appendAmbiguousLeafs(ambiguousLeafBranches, kbList);
                }
            }
        }
    }

    private void appendAmbiguousLeafs(Set<Branch> ambiguousLeafBranches,
            List<KnowledgeBase> kbList) {
        if (ambiguousLeafBranches != null) {
            for (Branch ambiguousLeafBranch : ambiguousLeafBranches) {
                appendAmbiguousLeafs(ambiguousLeafBranch, kbList);
            }
        }
    }

    private void appendAmbiguousLeafs(Branch ambiguousLeafBranch,
            List<KnowledgeBase> kbList) {
        if (ambiguousLeafBranch != null) {
            Set<Fragment> fragmentList = ambiguousLeafBranch
                    .getTerminatingFragmentList();
            if (fragmentList != null) {
                for (Fragment fragment : fragmentList) {
                    appendAmbiguousLeafs(fragment, kbList);
                }
            }
        }
    }

    private void appendAmbiguousLeafs(Fragment fragment,
            List<KnowledgeBase> kbList) {
        if (fragment != null && kbList != null) {
            List<Element> elements_BeforeAfterFragment = null;
            for (KnowledgeBase kb : kbList) {
                elements_BeforeAfterFragment = kb
                        .getElements_BeforeAfterFragment(fragment, orientation);
                if (elements_BeforeAfterFragment != null) {
                    break;
                }
            }
            if (elements_BeforeAfterFragment != null) {
                if (orientation == ORIENTATION.LEFT) {
                    Collections.reverse(elements_BeforeAfterFragment);
                }
                doContextAppend(fragment, elements_BeforeAfterFragment);
            }
        }
    }

    private void doContextAppend(Fragment fragment, List<Element> elements) {
        String newCxt = getContext(fragment);
        for (Element e : elements) {
            String append = "";
            if (e.getType() == Element.ElementType.ENTITY) {
                append = UTEES_Index.tag(e.getLabelIndex());
            } else {
                append = e.getValue();
            }
            if (append == null)
                return;
            append = reverseStringIfLeft(append);
            if (e.getType() == Element.ElementType.ENTITY
                    || e.getType() == Element.ElementType.FILLER) {
                newCxt += append;
                Stem stem = put(reverseStringIfLeft(append), fragment);
                ElementOntologySet branchID = fragment.getOntologies();
                Branch childByID = stem.childByID(branchID);
                if (!childByID.isAmbiguous()) {
                    stem.updateFruit_Terminator(branchID, fragment);
                    return;
                }
            } else {
                for (int i = 0; i < append.length(); i++) {
                    newCxt += append.charAt(i);
                    Stem stem = put(reverseStringIfLeft(append), fragment);
                    ElementOntologySet branchID = fragment.getOntologies();
                    Branch childByID = stem.childByID(branchID);
                    if (!childByID.isAmbiguous()) {
                        stem.updateFruit_Terminator(branchID, fragment);
                        return;
                    }
                }
            }
        }
        Stem stem = get(newCxt);
        ElementOntologySet branchID = fragment.getOntologies();
        stem.updateFruit_Terminator(branchID, fragment);
    }

    protected void normalize(List<KnowledgeBase> kbList) {
        if (kbList != null && !kbList.isEmpty()) {
            updateTotalKeyParameterFreq(kbList);
            appendAmbiguousLeafs(kbList);
        }
    }
    
    
    public void updateTotalKeyParameterFreq(KnowledgeBase knowledgeBase) {
        Set<Map.Entry<String, Stem>> entrySet = this.entrySet();
        if (entrySet == null)
            return;
        for(Map.Entry<String, Stem> e: entrySet){
            int freq = knowledgeBase.countStringOccurence(e.getKey(), orientation);
//            System.out.println("String #" + count++ + e.getKey() + "--->" + freq);
            this.put(e.getKey(), freq, null);
            
        }
    }

    public void updateTotalKeyParameterFreq(List<KnowledgeBase> kbList) {
        if(kbList == null || kbList.isEmpty())
            return;
        Set<Map.Entry<String, Stem>> entrySet = this.entrySet();
        if (entrySet == null)
            return;
        
        for(Map.Entry<String, Stem> e: entrySet){
            UpdateTotalKeyParameterFreqOperation op = 
                new UpdateTotalKeyParameterFreqOperation(e.getKey(), orientation);
            int freq = IterationHelper.runIteration("UpdateTotalKeyParameterFreqOperation", kbList, op, 0, kbList.size());
            this.put(e.getKey(), freq, null);
        }
    }

    private static class UpdateTotalKeyParameterFreqOperation implements IterationOperation<KnowledgeBase, Integer, Integer> {
        private String string;
        private ORIENTATION orientation;

        public UpdateTotalKeyParameterFreqOperation(String string, ORIENTATION orientation) {
            this.string = string;
            this.orientation = orientation;
        }

        @Override
        public Integer compute(int index, KnowledgeBase val) {
            return val.countStringOccurence(string, orientation);
        }
        
        @Override
        public Integer aggregate(
                Integer current, 
                int index, 
                Integer result) {        
            return current + result;
        }
    }
    

    public Set<Map.Entry<String, Stem>> getEntrySet_CONTEXT(BOOTSTRAP bootStrap) {
        if (bootStrap == BOOTSTRAP.YES) {
            return this.leafEntrySet();
        } else {
            return this.nonAmbiguousEntrySet();
        }
    }

    public Set<ContextCandidate> findContexts(String inString,
            BOOTSTRAP bootStrap) {
        Set<ContextCandidate> result = null;
        inString = this.reverseStringIfLeft(inString);
        if (inString != null) {
            result = new HashSet<ContextCandidate>();
            Set<Map.Entry<String, Stem>> possibleContextSet = getEntrySet_CONTEXT(bootStrap);
            if (possibleContextSet != null) {
                for (Map.Entry<String, Stem> possibleCxt : possibleContextSet) {
                    result.addAll(findContexts(inString, possibleCxt));
                }
            }
        }
        return result;
    }

    private Set<ContextCandidate> findContexts(String inString,
            Map.Entry<String, Stem> possibleCxt) {
        Set<ContextCandidate> result = null;
        if (inString != null && possibleCxt != null) {
            result = new HashSet<ContextCandidate>();
            // ???if(UTEES_Index.isTaggedText(possibleCxt.getKey()))
            if (!(possibleCxt.getKey()
                    .contains(UTEES_Index.TAG_START_INDICATOR) ^ possibleCxt
                    .getKey().contains(UTEES_Index.TAG_END_INDICATOR))) {
                Matcher m = Pattern.compile(possibleCxt.getKey(),
                        Pattern.LITERAL).matcher(inString);
                if (m.find()) {
                    m.reset();
                    Stem stem = possibleCxt.getValue();
                    Element ele = stem.getFragmentSample().getContextElement(
                            orientation);
                    result.add(new ContextCandidate(ele, stem, m));
                }
            }
        }
        return result;
    }

}